package com.googlecode.flex_solder.controls  {

  /**
   * $Id: BaseMultiCombo.as 16 2009-01-03 03:38:29Z fuller.sethm $
   *
   * Author: Seth M. Fuller
   * Copyright: 2008
   *
   *  This class is heavily based on the BaseMultiFieldInput class in
   *  the Yahoo Astra Flex components library. I modified it for use
   *  with ComboBox controls, as opposed to the UITextField controls
   *  used in BaseMultiFieldInput.
   *
   *  Permission is hereby granted, free of charge, to any person
   *  obtaining a copy of this software and associated documentation
   *  files (the "Software"), to deal in the Software without
   *  restriction, including without limitation the rights to use,
   *  copy, modify, merge, publish, distribute, sublicense, and/or
   *  sell copies of the Software, and to permit persons to whom the
   *  Software is furnished to do so, subject to the following
   *  conditions:
   *
   *  The above copyright notice and this permission notice shall be
   *  included in all copies or substantial portions of the Software.
   *
   *  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
   *  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
   *  OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
   *  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
   *  HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
   *  WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
   *  FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
   *  OTHER DEALINGS IN THE SOFTWARE.
   *
   * This file is licensed under the BSD (revised) open source
   * license.
   *
   * This is part of the flex-solder components hosted at
   * flex-solder.googlecode.com.
   *
   *======================================================================
   *
   * Contains third-party source code released with the same license terms
   * that appear above with the following copyright notice:
   *
   * Copyright (c) 2008 Yahoo! Inc.  All rights reserved.  The copyrights
   * embodied in the content of this file are licensed under the BSD
   * (revised) open source license
   */

  import flash.display.DisplayObject;
  import flash.events.Event;
  import flash.events.FocusEvent;
  import flash.events.KeyboardEvent;
  import flash.ui.Keyboard;
	
  import mx.collections.ArrayCollection;
  import mx.core.IUIComponent;
  import mx.core.EdgeMetrics;
  import mx.core.IBorder;
  import mx.core.IFlexDisplayObject;
  import mx.core.IInvalidating;
  import mx.core.UIComponent;
  import mx.core.Container;

  import mx.containers.utilityClasses.Flex;

  import mx.controls.ComboBox;
  import mx.events.FlexEvent;
  import mx.managers.IFocusManagerContainer;
  import mx.styles.CSSStyleDeclaration;
  import mx.styles.ISimpleStyleClient;
  import mx.styles.StyleManager;
	
  /**
   * Number of pixels between the component's bottom border
   * and the bottom edge of its content area.
   *
   * @default 0
   */
  [Style(name="paddingBottom", type="Number", format="Length", inherit="no")]
	
    /**
     * Number of pixels between the component's top border
     * and the top edge of its content area.
     *  
     * @default 0
     */
    [Style(name="paddingTop", type="Number", format="Length", inherit="no")]

    //--------------------------------------
    //  Other Metadata
    //--------------------------------------

    [DefaultTriggerEvent("change")]

    public class BaseMultiCombo extends Container 
    implements IFocusManagerContainer {

    protected static var comboPadLeft:Number = 2;
    protected static var comboPadRight:Number = 0;

    public static var downArrowWidth:Number = 0;
    public static var comboWidth:Number = 0;
    public static var comboPercentWidth:Number = 23;
    public static var comboHeight:Number = 25;
    public static var comboHeightPad:Number = 5;
    public static var comboWidthPad:Number = 9;
    public static var comboPercentHeight:Number = 100;

    //--------------------------------------
    //  Static Methods
    //--------------------------------------
	
    /**
     * @private
     * Sets the default style values for this control.
     */
    public static function initializeStyles():void
    {
      var styleDeclaration:CSSStyleDeclaration
	= StyleManager.getStyleDeclaration("BaseMultiCombo");
      if(!styleDeclaration)
	{
	  styleDeclaration = new CSSStyleDeclaration();
	}
			
      styleDeclaration.defaultFactory = function():void
      {

	//other styles are based on the framework defaults
      };
			
      StyleManager.setStyleDeclaration("BaseMultiCombo", 
				       styleDeclaration, false);

    }
    initializeStyles();
		
    public function BaseMultiCombo() {
      super();
    }

    //--------------------------------------
    //  Properties
    //--------------------------------------
		
    /**
     * The number of editable fields in this control. This value must be set
     * by a subclass before <code>createChildren()</code> is called.
     */
    protected var fieldCount:int = 1;
		
    /**
     * The internal subcontrol that draws the border and background.
     */
    protected var border:IFlexDisplayObject;
    
    /**
     * The internal input ComboBoxes.
     */
    protected var comboBoxes:Array = [];
		
    /**
     * @private
     * Flag that indicates that the enabled property has changed.
     */
    protected var enabledChanged:Boolean = false;
		
    [Inspectable(category="General", enumeration="true,false", defaultValue="true")]
      /**
       * @private
       * Disable ComboBox when we're disabled.
       */
      override public function set enabled(value:Boolean):void {
      if(value == this.enabled)
	{
	  return;
	}
		
      super.enabled = value;
      this.enabledChanged = true;
		
      this.invalidateProperties();
		
      if(this.border && this.border is IInvalidating)
	{
	  IInvalidating(this.border).invalidateDisplayList();
	}

    }
		
    /**
     * @private
     * Storage for the editable property.
     */
    private var _editable:Boolean = true;
		
    /**
     * @private
     * Flag that indicates that the editable property has changed.
     */
    protected var editableChanged:Boolean = false;
		
    [Bindable("editableChanged")]
      [Inspectable(category="General", defaultValue="true")]
      /**
       * Indicates whether the user is allowed to edit the text in this control.
       * If <code>true</code>, the user can edit the text.
       *
       * @default true
       * 
       * @tiptext Specifies whether the component is editable or not
       * @helpid 3196
       */
      public function get editable():Boolean
      {
	return this._editable;
      }
		
    /**
     * @private
     */
    public function set editable(value:Boolean):void
    {
      if(value == this._editable)
	{
	  return;
	}
			
      this._editable = value;
      this.editableChanged = true;
			
      this.invalidateProperties();
			
      this.dispatchEvent(new Event("editableChanged"));
    }
		

    //--------------------------------------
    //  Public Methods
    //--------------------------------------
	

    //--------------------------------------
    //  Protected Methods
    //--------------------------------------

    protected function getNumberList(numLimit:int, numInc:int, 
				  numStart:int = 0):ArrayCollection {
      var numberList:ArrayCollection = new ArrayCollection();

      for (var numVal:int = numStart; numVal <= numLimit; numVal += numInc) {
	numberList.addItem(numVal);
      }

      return numberList;
    }
	
    protected function getNumberValue(numVal:Number):String {

      var numValStr:String = null;

      if (isNaN(numVal) || numVal < 0) {
	numValStr = "";
      }
      else if (numVal < 10) {
	numValStr = "0" + numVal;
      }
      else {
	numValStr = "" + numVal;
      }

      return numValStr;
    }

    /**
     * @private
     */
    override protected function createChildren():void
    {
      super.createChildren();
			

    }
	
    /** 
     * Creates a ComboBox, sets it left and right padding
     * and adds it as a child to this container.
     * 
     * @return ComboBox
     */
    protected function createComboBox():ComboBox {

      var comboBox:ComboBox = new ComboBox();
      comboBox.enabled = this.enabled;
      comboBox.styleName = this;

      comboBox.setStyle("paddingLeft", comboPadLeft);
      comboBox.setStyle("paddingRight", comboPadRight);
	            
      comboBox.addEventListener(KeyboardEvent.KEY_DOWN, 
				keyDownNavigationHandler, false, 1000);
      comboBox.addEventListener(Event.CHANGE, comboBoxChangeHandler);

      this.addChild(comboBox);
      this.comboBoxes.push(comboBox);
      return comboBox;
    }
	
    /**
     * @private
     */
    override protected function commitProperties():void {
      super.commitProperties();
			
      if(this.enabledChanged || this.editableChanged)
	{
	  var tfCount:int = this.comboBoxes.length;
	  for(var idx:int = 0; idx < tfCount; idx++)
	    {
	      var comboBox:ComboBox = ComboBox(this.comboBoxes[idx]);
	      if(this.enabledChanged)
		{
		  comboBox.enabled = this.enabled;
		}
	    }
			
	  this.enabledChanged = false;
	  this.editableChanged = false;
	}

    }
		
    /**
     * @private
     */
    override protected function
      updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void
    {
      super.updateDisplayList(unscaledWidth, unscaledHeight);
			
      if (this.border != null) {
	this.border.setActualSize(unscaledWidth, unscaledHeight);
      }


       this.layoutChildren();
    }
		

    /**
     * @private
     * Positions the Children within the content area.
     */
    protected function layoutChildren():void {
      var metrics:EdgeMetrics = (this.border != null && this.border is IBorder 
				 ? IBorder(this.border).borderMetrics
				 : EdgeMetrics.EMPTY);
			
      var tfHeight:Number = Math.max(0, unscaledHeight - metrics.top 
				     - metrics.bottom);
      var xPosition:Number = metrics.left;
      var tfCount:int = this.comboBoxes.length;
      for(var idx:int = 0; idx < tfCount; idx++) {
	var tf:ComboBox = ComboBox(this.comboBoxes[idx]);
	tf.x = xPosition;
	tf.y = metrics.top;
				
	//the sizing here means that small enough dimensions will cause some text to be cut off and inaccessible.
	//it is highly recommended never to make this control smaller than the measured dimensions
	// 	tf.setActualSize(Math.min(Math.max(0, unscaledWidth - metrics.right - xPosition), tf.measuredWidth), tfHeight);
	xPosition += tf.measuredWidth;
      }
    }

    /**
     * @private
     */
    override protected function isOurFocus(target:DisplayObject):Boolean {
      var tfCount:int = this.comboBoxes.length;
      for(var idx:int = 0; idx < tfCount; idx++)
	{
	  var comboBox:ComboBox = ComboBox(this.comboBoxes[idx]);
	  if (target == comboBox) {
	    return true;
	  }
	}
      return super.isOurFocus(target);
    }
		
    /**
     * @private
     * 
     * Takes a ComboBox as input and finds the next editable ComboBox to focus.
     * If the input ComboBox is the last in the control, focus will not be changed.
     * Throws an ArgumentError if the input ComboBox is one of the "dot" comboBoxes.
     */
    protected function focusNextComboBox(comboBox:ComboBox):void
    {
      var index:int = this.comboBoxes.indexOf(comboBox);
      if(index < 0) throw new ArgumentError("Invalid ComboBox");
			
      if(index == this.comboBoxes.length - 1) return;
      else index++;
			
      comboBox = ComboBox(this.comboBoxes[index]);
      //if we call it now, the keyboard event will cause problems.
      //by calling it later, we can get the correct index and select the comboBox too.
      this.callLater(comboBox.setFocus);
    }
		
    /**
     * @private
     * 
     * Takes a ComboBox as input and finds the previous editable ComboBox to focus.
     * If the input ComboBox is the first in the control, focus will not be changed.
     * Throws an ArgumentError if the input ComboBox is one of the "dot" comboBoxes.
     */
    protected function focusPreviousComboBox(comboBox:ComboBox):void
    {
      var index:int = this.comboBoxes.indexOf(comboBox);
      if(index < 0) throw new ArgumentError("Invalid ComboBox");
			
      if(index == 0) return;
      else index--;
			
      comboBox = ComboBox(this.comboBoxes[index]);
      //if we call it now, the keyboard event will cause problems.
      //by calling it later, we can get the correct index and select the comboBox too.
      this.callLater(comboBox.setFocus);
    }
		
	
    //--------------------------------------
    //  Protected Event Handlers
    //--------------------------------------
		

    /**
     * @private
     * Special navigation between ComboBoxes when LEFT and RIGHT keys are pressed.
     */

    protected function keyDownNavigationHandler(event:KeyboardEvent):void
    {
      var comboBox:ComboBox = ComboBox(event.target);
      switch(event.keyCode)
	{
	case Keyboard.RIGHT:
	  this.focusNextComboBox(comboBox);

	  break;
	case Keyboard.LEFT:
	  this.focusPreviousComboBox(comboBox);

	  break;
	case Keyboard.ENTER:
	  this.dispatchEvent(new FlexEvent(FlexEvent.ENTER));
	  break;
	}
    }

    public function getComboHeight():Number {
      return BaseMultiCombo.comboHeight;
    }

   /**
     * @private
     * Stops ComboBox change events from bubbling.
     */
    protected function comboBoxChangeHandler(event:Event):void
    {
      event.stopPropagation();
    }
  }
}